package no.ntnu.fp.su;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;

import no.ntnu.fp.db.Database;
import no.ntnu.fp.mmi.WatchModel;
import no.ntnu.fp.net.co.Connection;
import no.ntnu.fp.net.co.ConnectionImpl;
import no.ntnu.fp.net.co.ReceiveConnectionWorker;
import no.ntnu.fp.net.co.ReceiveMessageWorker;
import no.ntnu.fp.net.co.ReceiveConnectionWorker.ConnectionListener;
import no.ntnu.fp.net.co.ReceiveMessageWorker.MessageListener;

/**
 * Watch class, main object for Watch Application
 * 
 * @author G208
 * 
 */
public class Watch extends Thread implements PropertyChangeListener {

	public static final String SEPARATOR = "#";
	private static final int SERVER_PORT = 10500;
	private static final String SERVER_ADRESS = "78.91.9.14";

	private Platform platform;

	/**
	 * Gets the Platform associated with this Watch
	 * 
	 * @return {@link Platform}
	 */
	public Platform getPlatform() {
		return platform;
	}

	private ArrayList<Message> messages;
	// private WatchModel guiModel; - hvorfor er dette her?
	private Connection inConnection;

	private boolean isRunning;
	private boolean connected;
	private boolean isWatch;

	private Master master;

	/**
	 * Tests if this object is a normal Watch (and therefore actually runs any
	 * associated {@link WatchWell})
	 * 
	 * @return False if this watch is launched through a {@link Master}
	 */
	public boolean isWatch() {
		return isWatch;
	}

	private ReceiveMessageWorker rmw;
	private ReceiveConnectionWorker rcw;
	private Connection outConnection;

	/**
	 * Constructor for Watch, for Watch
	 * 
	 * @param id
	 *            - Platform ID
	 * 
	 */
	public Watch(int id) {
		// platform = new Platform(this);
		isWatch = true;
		System.out.println("WATCH");
		loadPlatform(id);
		inConnection = new ConnectionImpl(15000 + platform.getID());
		outConnection = new ConnectionImpl();
		connected = false;
		rcw = new ReceiveConnectionWorker(inConnection, new ConnectionWorker(
				this));

	}

	/**
	 * Constructor meant for {@link Master}, will not start any connection
	 * threads
	 * 
	 * @param {@link Platform} - Platform this Watch is for
	 * @param {@link Connection} - The receive connection for this Watch
	 * @param {@link Connection} - The send connection for this Watch
	 * @param {@link Master} - The Master
	 */
	public Watch(Platform plat, Connection in, Connection out, Master master) {
		isWatch = false;
		System.out.println("MASTER");
		// masterWatchRunning = watchRunning;
		inConnection = in;
		outConnection = out;
		this.connected = true;
		this.master = master;

		platform = plat; // loadPlatform(id);

	}

	/**
	 * Loads the specified {@link PLatform} from the {@link Database}.
	 * 
	 * @param id - Platform id to load
	 */
	private void loadPlatform(int id) {
		Database db = new Database();
		ResultSet rs;

		String navn;

		db.connect();
		rs = db.query("SELECT navn FROM platform WHERE platformid = " + id);
		try {
			while (rs.next()) {
				navn = rs.getString(1);
				System.out.println(navn);

				platform = new Platform(id, navn, isWatch);
				platform.addPropertyChangeListener(this);

			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Entry point for Watch.
	 * 
	 */
	public void run() {
		isRunning = true;

		if (isWatch) {
			platform.start();
		} else {
			master.setPlatformWatchRunning(true);
		}

		/*WatchModel runWatch =*/ new WatchModel(this);

		if (isWatch) {
			rcw.start();
			connectToServer();
		}

		while (isRunning) {
			try {
				sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if (!isWatch) {
			master.setPlatformWatchRunning(false);
		}
		System.out.println("Watch done, master.getPlatformWatchRunning = "
				+ master.getPlatformWatchRunning());

	}

	/**
	 * Stopper watch tråden
	 * 
	 * @param boolean - skal platform og alle brønner også stoppes?
	 */
	public synchronized void stopRunning(boolean recursive) {
		System.out
				.println("Stopping watch!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!111111111oneoneone");
		if (isWatch) {
			if (recursive) {
				platform.stopRunning();
			}

			try {
				outConnection.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			try {
				inConnection.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			rcw.stopRunning();
			rmw.stopRunning();
		}

		do {
			isRunning = false;
		} while (isRunning);
	}

	/**
	 * Sender en beskjed til andre siden
	 * 
	 * @param {@link String} - Beskjed som skal sendes
	 * @return boolean - Message successfully sent
	 */
	public boolean sendMessage(String s) {
		Message message = new Message();
		message.setText(s);
		message.setTimestamp(new Timestamp(System.currentTimeMillis()));
		if (isWatch) {
			message.setFrom(this.platform);
		} else {
			message.setFrom(null);
		}
		return sendMessage(message);

	}

	/**
	 * Decodes a received {@link String} to a {@link Message} object
	 * 
	 * @param {@link String} - packetBody
	 * @return {@link Message} - decoded String
	 */
	public static Message decodeMessage(String packetBody) {
		// decomp[] -> "message" - "from" - "timestamp(milliseconds)" -
		// "<the message>"
		String[] decomp = packetBody.split(SEPARATOR);
		Message msg = new Message();
		msg.setFrom(null); // null = Master
		msg.setTimestamp(new Timestamp(Long.parseLong(decomp[2])));
		msg.setText(decomp[3]);
		return msg;
	}

	/**
	 * Sender beskjed til andre siden
	 * 
	 * @param {@link Message} - Message som skal sendes
	 * @return boolean - Message successfully sent
	 */
	private boolean sendMessage(Message message) {
		if (outConnection == null) {
			System.out.println("not connected, cant send");
			return false;
		}
		try {
			outConnection.send(message.toString());
		} catch (ConnectException e) {
			System.out.println("not connected, can't send");
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			System.out.println("IO error, can't send");
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * Attempts to connect to a {@link Master}
	 * 
	 * @return boolean - connected successfully
	 */
	private boolean connectToServer() {
		if (!isWatch)
			return false;
		int tries = 3;
		while (!connected) {
			try {
				outConnection.connect(SERVER_ADRESS, SERVER_PORT);
				connected = true;
			} catch (SocketTimeoutException e) {
				if (--tries < 0)
					return false;
				e.printStackTrace();
			} catch (IOException e) {
				System.out.println("IOException in connect to server");
				e.printStackTrace();
			}
		}
		try {
			outConnection.send("Platform" + SEPARATOR + platform.getID()
					+ SEPARATOR + platform.getName() + SEPARATOR
					+ ((ConnectionImpl) outConnection).getIPv4Address()
					+ SEPARATOR + (15000 + platform.getID()));
		} catch (ConnectException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true;

	}

	/**
	 * Adds a {@link Message} to the message list
	 * @param {@link Message}
	 */
	public void addMessage(Message message) {
		messages.add(message);

	}

	/**
	 * FOR DEBUG ONLY
	 * 
	 * @param args
	 * @deprecated
	 */
	public static void main(String[] args) {
		Watch mainW;
		System.out.println("launching Watch");
		mainW = new Watch(1);
		mainW.start();

		System.out.println("Main done");
	}

	/**
	 * Hjelpeklasse for å behandle motatte connections
	 * 
	 * @author stn
	 * 
	 */
	class ConnectionWorker implements ConnectionListener {

		private Watch watch;

		/**
		 * Constructor
		 * 
		 * @param {@link Master} - parent
		 */
		public ConnectionWorker(Watch parent) {
			watch = parent;
		}

		/**
		 * Method that is called when a new connection is received
		 */
		@Override
		public void connectionReceived(Connection connection) {

			watch.inConnection = connection;
			watch.rmw = new ReceiveMessageWorker(watch.inConnection);
			watch.rmw.start();
			watch.rcw.stopRunning();
		}

	}

	/**
	 * Hjelpeklasse for å behandle motatte meldinger fra platformer
	 * 
	 * @author stn
	 * 
	 */
	class MessageWorker implements MessageListener {
		private Watch watch;

		/**
		 * Constructor
		 * 
		 * @param {@link Connection} - connectionen som skal høres på
		 * @param {@link Master} - parent
		 */
		public MessageWorker(Watch parent) {
			watch = parent;
			watch.rmw.addMessageListener(this);
		}

		/**
		 * Method that is called when the connection is closed
		 */
		@Override
		public void connectionClosed(Connection conn) {
			try {
				conn.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			watch.rcw.start();

		}

		/**
		 * Method that is called when a mesage is received
		 */
		@Override
		public void messageReceived(String message) {
			if (message == null)
				return;
			String[] msg = message.split(SEPARATOR);
			if (msg[0].equals("message")) {
				addMessage(decodeMessage(message));

			}

		}

	}

	@Override
	public void propertyChange(PropertyChangeEvent ev) {
		if (ev.getPropertyName().equals("newEvent")) {
			try {
				outConnection.send("newEvent" + SEPARATOR + ".");
			} catch (ConnectException e) {
				System.out.println("not connected, can't send");
				e.printStackTrace();
			} catch (IOException e) {
				System.out.println("IO error, can't send");
				e.printStackTrace();
			}
		}

	}

}
